// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System;
using System.IO;
using System.Linq;
using System.Net;
using SharpCifs.Netbios;
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;

namespace SharpCifs
{
	/// <summary>
	/// <p>Under normal conditions it is not necessary to use
	/// this class to use jCIFS properly.
	/// </summary>
	/// <remarks>
	/// <p>Under normal conditions it is not necessary to use
	/// this class to use jCIFS properly. Name resolusion is
	/// handled internally to the <code>jcifs.smb</code> package.
	/// <p>
	/// This class is a wrapper for both
	/// <see cref="Jcifs.Netbios.NbtAddress">Jcifs.Netbios.NbtAddress</see>
	/// and
	/// <see cref="System.Net.IPAddress">System.Net.IPAddress</see>
	/// . The name resolution mechanisms
	/// used will systematically query all available configured resolution
	/// services including WINS, broadcasts, DNS, and LMHOSTS. See
	/// <a href="../../resolver.html">Setting Name Resolution Properties</a>
	/// and the <code>jcifs.resolveOrder</code> property. Changing
	/// jCIFS name resolution properties can greatly affect the behavior of
	/// the client and may be necessary for proper operation.
	/// <p>
	/// This class should be used in favor of <tt>InetAddress</tt> to resolve
	/// hostnames on LANs and WANs that support a mixture of NetBIOS/WINS and
	/// DNS resolvable hosts.
	/// </remarks>
	public class UniAddress
	{
		private const int ResolverWins = 0;

		private const int ResolverBcast = 1;

		private const int ResolverDns = 2;

		private const int ResolverLmhosts = 3;

		private static int[] _resolveOrder;

		private static IPAddress _baddr;

		private static LogStream _log = LogStream.GetInstance();

		static UniAddress()
		{
			string ro = Config.GetProperty("jcifs.resolveOrder");
			IPAddress nbns = NbtAddress.GetWinsAddress();
			try
			{
				_baddr = Config.GetInetAddress("jcifs.netbios.baddr", Extensions.GetAddressByName
					("255.255.255.255"));
			}
			catch (UnknownHostException)
			{
			}
			if (string.IsNullOrEmpty(ro))
			{
				if (nbns == null)
				{
					_resolveOrder = new int[3];
					_resolveOrder[0] = ResolverLmhosts;
					_resolveOrder[1] = ResolverDns;
					_resolveOrder[2] = ResolverBcast;
				}
				else
				{
					_resolveOrder = new int[4];
					_resolveOrder[0] = ResolverLmhosts;
					_resolveOrder[1] = ResolverWins;
					_resolveOrder[2] = ResolverDns;
					_resolveOrder[3] = ResolverBcast;
				}
			}
			else
			{
				int[] tmp = new int[4];
				StringTokenizer st = new StringTokenizer(ro, ",");
				int i = 0;
				while (st.HasMoreTokens())
				{
					string s = st.NextToken().Trim();
					if (Runtime.EqualsIgnoreCase(s, "LMHOSTS"))
					{
						tmp[i++] = ResolverLmhosts;
					}
					else
					{
						if (Runtime.EqualsIgnoreCase(s, "WINS"))
						{
							if (nbns == null)
							{
								if (_log.Level > 1)
								{
									_log.WriteLine("UniAddress resolveOrder specifies WINS however the " + "jcifs.netbios.wins property has not been set"
										);
								}
								continue;
							}
							tmp[i++] = ResolverWins;
						}
						else
						{
							if (Runtime.EqualsIgnoreCase(s, "BCAST"))
							{
								tmp[i++] = ResolverBcast;
							}
							else
							{
								if (Runtime.EqualsIgnoreCase(s, "DNS"))
								{
									tmp[i++] = ResolverDns;
								}
								else
								{
									if (_log.Level > 1)
									{
										_log.WriteLine("unknown resolver method: " + s);
									}
								}
							}
						}
					}
				}
				_resolveOrder = new int[i];
				Array.Copy(tmp, 0, _resolveOrder, 0, i);
			}
		}

		internal class Sem
		{
			internal Sem(int count)
			{
				this.Count = count;
			}

			internal int Count;
		}

		internal class QueryThread : Thread
		{
			internal Sem Sem;

			internal string Host;

			internal string Scope;

			internal int Type;

			internal NbtAddress[] Ans;

			internal IPAddress Svr;

			internal UnknownHostException Uhe;

			internal QueryThread(Sem sem, string host, int type, string scope, IPAddress
				 svr) : base("JCIFS-QueryThread: " + host)
			{
				this.Sem = sem;
				this.Host = host;
				this.Type = type;
				this.Scope = scope;
				this.Svr = svr;
			}

			public override void Run()
			{
				try
				{
				    //Ans = new [] { NbtAddress.GetByName(Host, Type, Scope, Svr) };
				    Ans = NbtAddress.GetAllByName(Host, Type, Scope, Svr);
				}
				catch (UnknownHostException uhe)
				{
					this.Uhe = uhe;
				}
				catch (Exception ex)
				{
					Uhe = new UnknownHostException(ex.Message);
				}
				finally
				{
					lock (Sem)
					{
						Sem.Count--;
						Runtime.Notify(Sem);
					}
				}
			}
		}

		/// <exception cref="UnknownHostException"></exception>
		internal static NbtAddress[] LookupServerOrWorkgroup(string name, IPAddress svr)
		{
			Sem sem = new Sem(2);
			int type = NbtAddress.IsWins(svr) ? unchecked(0x1b) : unchecked(0x1d);
			QueryThread q1X = new QueryThread(sem, name, type, null, svr
				);
			QueryThread q20 = new QueryThread(sem, name, unchecked(0x20), null, svr);
			q1X.SetDaemon(true);
			q20.SetDaemon(true);
			try
			{
				lock (sem)
				{
					q1X.Start();
					q20.Start();
					while (sem.Count > 0 && q1X.Ans == null && q20.Ans == null)
					{
						Runtime.Wait(sem);
					}
				}
			}
			catch (Exception)
			{
				throw new UnknownHostException(name);
			}
			if (q1X.Ans != null)
			{
				return q1X.Ans;
			}
		    if (q20.Ans != null)
		    {
		        return q20.Ans;
		    }
		    throw q1X.Uhe;
		}

		/// <summary>Determines the address of a host given it's host name.</summary>
		/// <remarks>
		/// Determines the address of a host given it's host name. The name can be a
		/// machine name like "jcifs.samba.org",  or an IP address like "192.168.1.15".
		/// </remarks>
		/// <param name="hostname">NetBIOS or DNS hostname to resolve</param>
		/// <exception cref="UnknownHostException">if there is an error resolving the name
		/// 	</exception>
		public static UniAddress GetByName(string hostname)
		{
			return GetByName(hostname, false);
		}

		internal static bool IsDotQuadIp(string hostname)
		{
			if (char.IsDigit(hostname[0]))
			{
				int i;
				int len;
				int dots;
				char[] data;
				i = dots = 0;
				len = hostname.Length;
				data = hostname.ToCharArray();
				while (i < len && char.IsDigit(data[i++]))
				{
					if (i == len && dots == 3)
					{
						// probably an IP address
						return true;
					}
					if (i < len && data[i] == '.')
					{
						dots++;
						i++;
					}
				}
			}
			return false;
		}

		internal static bool IsAllDigits(string hostname)
		{
			for (int i = 0; i < hostname.Length; i++)
			{
				if (char.IsDigit(hostname[i]) == false)
				{
					return false;
				}
			}
			return true;
		}

		/// <summary>Lookup <tt>hostname</tt> and return it's <tt>UniAddress</tt>.</summary>
		/// <remarks>
		/// Lookup <tt>hostname</tt> and return it's <tt>UniAddress</tt>. If the
		/// <tt>possibleNTDomainOrWorkgroup</tt> parameter is <tt>true</tt> an
		/// addtional name query will be performed to locate a master browser.
		/// </remarks>
		/// <exception cref="UnknownHostException"></exception>
		public static UniAddress GetByName(string hostname, bool possibleNtDomainOrWorkgroup
			)
		{
			UniAddress[] addrs = GetAllByName(hostname, possibleNtDomainOrWorkgroup
				);
			return addrs[0];
		}

		/// <exception cref="UnknownHostException"></exception>
		public static UniAddress[] GetAllByName(string hostname, bool possibleNtDomainOrWorkgroup
			)
		{
			object addr;
			int i;
			if (string.IsNullOrEmpty(hostname))
			{
				throw new UnknownHostException();
			}
			if (IsDotQuadIp(hostname))
			{
				UniAddress[] addrs = new UniAddress[1];
				addrs[0] = new UniAddress(NbtAddress.GetByName(hostname));
				return addrs;
			}
			for (i = 0; i < _resolveOrder.Length; i++)
			{
				try
				{
					switch (_resolveOrder[i])
					{
						case ResolverLmhosts:
						{
							if ((addr = Lmhosts.GetByName(hostname)) == null)
							{
								continue;
							}
							break;
						}

						case ResolverWins:
						{
							if (hostname == NbtAddress.MasterBrowserName || hostname.Length > 15)
							{
								// invalid netbios name
								continue;
							}
							if (possibleNtDomainOrWorkgroup)
							{
								addr = LookupServerOrWorkgroup(hostname, NbtAddress.GetWinsAddress());
							}
							else
							{
								addr = NbtAddress.GetByName(hostname, unchecked(0x20), null, NbtAddress.GetWinsAddress
									());
							}
							break;
						}

						case ResolverBcast:
						{
							if (hostname.Length > 15)
							{
								// invalid netbios name
								continue;
							}

						    try
						    {
                                if (possibleNtDomainOrWorkgroup)
                                {
                                    NbtAddress[] iaddrs = LookupServerOrWorkgroup(hostname, _baddr);

                                    UniAddress[] addrs = new UniAddress[iaddrs.Length];
                                    for (int ii = 0; ii < iaddrs.Length; ii++)
                                    {
                                        addrs[ii] = new UniAddress(iaddrs[ii]);
                                    }
                                    return addrs;

                                }
                                else
                                {
                                    addr = NbtAddress.GetByName(hostname, unchecked(0x20), null, _baddr);
                                }

						    }
						    catch (Exception ex)
						    {
						        if (i == _resolveOrder.Length - 1)
						        {
						            throw ex;
						        }
						        else
						        {
						            continue;
						        }
						    }
							break;
						}

						case ResolverDns:
						{
							if (IsAllDigits(hostname))
							{
								throw new UnknownHostException(hostname);
							}

                            IPAddress[] iaddrs = Extensions.GetAddressesByName(hostname);

                            if (iaddrs == null || iaddrs.Length == 0)
                            {
                                continue;
                            }

                            return iaddrs.Select(iaddr => new UniAddress(iaddr)).ToArray();                            
						}

						default:
						{
							// Success
							throw new UnknownHostException(hostname);
						}
					}
					UniAddress[] addrs1 = new UniAddress[1];
					addrs1[0] = new UniAddress(addr);
					return addrs1;
				}
				catch (IOException)
				{
				}
			}
			// Success
			// Failure
			throw new UnknownHostException(hostname);
		}

		internal object Addr;

		internal string CalledName;

		/// <summary>
		/// Create a <tt>UniAddress</tt> by wrapping an <tt>InetAddress</tt> or
		/// <tt>NbtAddress</tt>.
		/// </summary>
		/// <remarks>
		/// Create a <tt>UniAddress</tt> by wrapping an <tt>InetAddress</tt> or
		/// <tt>NbtAddress</tt>.
		/// </remarks>
		public UniAddress(object addr)
		{
			if (addr == null)
			{
				throw new ArgumentException();
			}
			this.Addr = addr;
		}

		/// <summary>Return the IP address of this address as a 32 bit integer.</summary>
		/// <remarks>Return the IP address of this address as a 32 bit integer.</remarks>
		public override int GetHashCode()
		{
			return Addr.GetHashCode();
		}

		/// <summary>Compare two addresses for equality.</summary>
		/// <remarks>
		/// Compare two addresses for equality. Two <tt>UniAddress</tt>s are equal
		/// if they are both <tt>UniAddress</tt>' and refer to the same IP address.
		/// </remarks>
		public override bool Equals(object obj)
		{
			return obj is UniAddress && Addr.Equals(((UniAddress)obj).Addr);
		}

		/// <summary>Guess first called name to try for session establishment.</summary>
		/// <remarks>
		/// Guess first called name to try for session establishment. This
		/// method is used exclusively by the <tt>jcifs.smb</tt> package.
		/// </remarks>
		public virtual string FirstCalledName()
		{
			if (Addr is NbtAddress)
			{
				return ((NbtAddress)Addr).FirstCalledName();
			}
		    CalledName = ((IPAddress) Addr).GetHostAddress();
		    if (IsDotQuadIp(CalledName))
		    {
		        CalledName = NbtAddress.SmbserverName;
		    }
		    else
		    {
		        int i = CalledName.IndexOf('.');
		        if (i > 1 && i < 15)
		        {
		            CalledName = Runtime.Substring(CalledName, 0, i).ToUpper();
		        }
		        else
		        {
		            if (CalledName.Length > 15)
		            {
		                CalledName = NbtAddress.SmbserverName;
		            }
		            else
		            {
		                CalledName = CalledName.ToUpper();
		            }
		        }
		    }
		    return CalledName;
		}

		/// <summary>Guess next called name to try for session establishment.</summary>
		/// <remarks>
		/// Guess next called name to try for session establishment. This
		/// method is used exclusively by the <tt>jcifs.smb</tt> package.
		/// </remarks>
		public virtual string NextCalledName()
		{
			if (Addr is NbtAddress)
			{
				return ((NbtAddress)Addr).NextCalledName();
			}
		    if (CalledName != NbtAddress.SmbserverName)
		    {
		        CalledName = NbtAddress.SmbserverName;
		        return CalledName;
		    }
		    return null;
		}

		/// <summary>Return the underlying <tt>NbtAddress</tt> or <tt>InetAddress</tt>.</summary>
		/// <remarks>Return the underlying <tt>NbtAddress</tt> or <tt>InetAddress</tt>.</remarks>
		public virtual object GetAddress()
		{
			return Addr;
		}

		/// <summary>Return the hostname of this address such as "MYCOMPUTER".</summary>
		/// <remarks>Return the hostname of this address such as "MYCOMPUTER".</remarks>
		public virtual string GetHostName()
		{
			if (Addr is NbtAddress)
			{
				return ((NbtAddress)Addr).GetHostName();
			}
		    return ((IPAddress) Addr).GetHostAddress();
		}

		/// <summary>Return the IP address as text such as "192.168.1.15".</summary>
		/// <remarks>Return the IP address as text such as "192.168.1.15".</remarks>
		public virtual string GetHostAddress()
		{
			if (Addr is NbtAddress)
			{
				return ((NbtAddress)Addr).GetHostAddress();
			}
			return ((IPAddress)Addr).GetHostAddress();
		}

	    public virtual IPAddress GetHostIpAddress()
	    {
	        return (IPAddress) Addr;
	    }

		/// <summary>
		/// Return the a text representation of this address such as
		/// <tt>MYCOMPUTER/192.168.1.15</tt>.
		/// </summary>
		/// <remarks>
		/// Return the a text representation of this address such as
		/// <tt>MYCOMPUTER/192.168.1.15</tt>.
		/// </remarks>
		public override string ToString()
		{
			return Addr.ToString();
		}
	}
}
